﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
using System.Linq;
using System.Collections.ObjectModel;

namespace Utility.DataSystem
{
	#region field component
	public class FieldComponentAttribute : Attribute
	{
		public FieldComponentAttribute (string showNameSuffix)
		{
			ShowNameSuffix = showNameSuffix;
		}

		public string ShowNameSuffix { get; }
		public object combineType;
		public Type fieldType;
		public object min = null;
		public object max = null;
	}

	public enum FieldComponents
	{
		[FieldComponent ("/基础值")]
		Base = 0,
		[FieldComponent ("/基础覆写值")]
		BaseOverride,
		[FieldComponent ("/覆写值")]
		Override,

		//below are number fields only
		[FieldComponent ("/附加比例值", combineType = CombineTypes.Add)]
		AdditionalRatio,
		[FieldComponent ("/附加固定值", combineType = CombineTypes.Add)]
		AdditionalFixed,

		[FieldComponent ("/独立系数", combineType = CombineTypes.Multiply)]
		IndieMultiplier,
		[FieldComponent ("/独立附加值", combineType = CombineTypes.Add)]
		IndieAdditional,

		//[FieldComponent ("/最小值", combineType = CombineTypes.Add)]
		//Min,
		//[FieldComponent ("/最大值", combineType = CombineTypes.Add)]
		//Max,

		//[FieldComponent ("/基础结果值")]
		//BaseResult,
		[FieldComponent ("/结果值")]
		Result,
	}

	public static class FieldComponentsEX
	{
		public static FieldComponentAttribute GetAttribute (this FieldComponents type)
		{
			return type.GetType ().GetField (type.ToString ()).GetCustomAttribute<FieldComponentAttribute> ();
		}

		public static string GetKey (this FieldComponents type, string name)
		{
			if (type == FieldComponents.Result)
			{
				return name;
			}
			return $"{name}@{type}";
		}

		public static string GetShowNameSuffix (this FieldComponents type)
		{
			return type.GetAttribute ().ShowNameSuffix;
		}

		//null for use main field combiner
		public static DataValueCombiner GetCombiner (this FieldComponents type)
		{
			object value = type.GetAttribute ().combineType;
			return value == null ? null : DataValueCombiners.GetCombiner ((CombineTypes)value);
		}

		//null for use main field type
		public static Type GetFieldType (this FieldComponents type)
		{
			return type.GetAttribute ().fieldType;
		}

		public static object GetMin (this FieldComponents type)
		{
			return type.GetAttribute ().min;
		}

		public static object GetMax (this FieldComponents type)
		{
			return type.GetAttribute ().max;
		}


	}
	#endregion
	#region composition
	[Flags]
	public enum FieldComposition
	{
		//Custom
		None = 0,

		//components
		Base = 1 << FieldComponents.Base,
		BaseOverride = 1 << FieldComponents.BaseOverride,
		Override = 1 << FieldComponents.Override,

		AdditionalRatio = 1 << FieldComponents.AdditionalRatio,
		AdditionalFixed = 1 << FieldComponents.AdditionalFixed,

		IndieMultiplier = 1 << FieldComponents.IndieMultiplier,
		IndieAdditional = 1 << FieldComponents.IndieAdditional,

		//Min = 1 << FieldComponents.Min,
		//Max = 1 << FieldComponents.Max,

		//compositions
		[FieldComposition (typeof (decimal), typeof (bool), typeof (string), typeof (Enum))]
		Simple = Base,

		//[FieldComposition (typeof (decimal))]
		//SimpleMinMax = Base | Min | Max,

		[FieldComposition (typeof (decimal), typeof (bool), typeof (string), typeof (Enum))]
		Overrideable = Base | Override,

		[FieldComposition (typeof (decimal))]
		ComplexNumber = Base | BaseOverride | Override | AdditionalRatio | AdditionalFixed | IndieAdditional | IndieMultiplier,
	}

	public class FieldCompositionAttribute : Attribute
	{
		public Type[] AllowedTypes { get; }

		public FieldCompositionAttribute (params Type[] allowedTypes)
		{
			AllowedTypes = allowedTypes;
		}
	}

	public static class FieldCompositionsEX
	{
		static Dictionary<FieldComposition, FieldComponents[]> componentsDict = new Dictionary<FieldComposition, FieldComponents[]> ();

		public static bool ContainsComponent (this FieldComposition composition, FieldComponents component)
		{
			return composition.HasFlag ((FieldComposition)(1 << (int)component));
		}

		public static bool NoComposition (this FieldComposition composition)
		{
			return composition == FieldComposition.None;
		}

		public static FieldCompositionAttribute GetAttribute (this FieldComposition type)
		{
			return type.GetType ().GetField (type.ToString ()).GetCustomAttribute<FieldCompositionAttribute> ();
		}

		public static bool AllowsType (this FieldComposition composition, Type type)
		{
			if (composition == FieldComposition.None)
			{
				return true;
			}

			var attr = composition.GetAttribute ();
			if (attr != null)
			{
				var types = attr.AllowedTypes;
				if (type.IsEnum && types.Contains (typeof (Enum)))
				{
					return true;
				}
				if (attr.AllowedTypes.Contains (type))
				{
					return true;
				}
			}
			return false;
		}

		public static Func<IDataFieldContext, FieldQuery, DataValue> GetValueGetter (this FieldComposition composition, string key)
		{
			string key_base = FieldComponents.Base.GetKey (key);
			string key_baseOverride = FieldComponents.BaseOverride.GetKey (key);
			string key_override = FieldComponents.Override.GetKey (key);
			string key_extraRatio = FieldComponents.AdditionalRatio.GetKey (key);
			string key_extraFixed = FieldComponents.AdditionalFixed.GetKey (key);
			string key_multiplier = FieldComponents.IndieMultiplier.GetKey (key);
			string key_indieExtra = FieldComponents.IndieAdditional.GetKey (key);
			//string key_min = FieldComponents.Min.GetKey (key);
			//string key_max = FieldComponents.Max.GetKey (key);

			switch (composition)
			{
				case FieldComposition.None:
					return null;
				case FieldComposition.Simple:
					DataValue GetValue_Simple (IDataFieldContext context, FieldQuery query)
					{
						return context.GetValue (key_base, query.RefreshValue, query.Parameters);
					}
					return GetValue_Simple;
				//case FieldComposition.SimpleMinMax:
				//	DataValue GetValue_SimpleMinMax (IDataFieldContext context, FieldQuery query)
				//	{
				//		DataValue result = context.GetValue (key_base, query.RefreshValue, query.Parameters);
				//		if (result.HasValue)
				//		{
				//			DataValue min = context.GetValue (key_min, query.RefreshValue, query.Parameters);
				//			DataValue max = context.GetValue (key_max, query.RefreshValue, query.Parameters);
				//			result.Clamp (min, max);
				//		}
				//		return result;
				//	}
				//	return GetValue_SimpleMinMax;
				case FieldComposition.Overrideable:
					DataValue GetValue_OverrideAble (IDataFieldContext context, FieldQuery query)
					{
						DataValue override_val = context.GetValue (key_override, query.RefreshValue, query.Parameters);
						if (override_val.HasValue)
						{
							return override_val;
						}
						else
						{
							return context.GetValue (key_base, query.RefreshValue, query.Parameters);
						}
					}
					return GetValue_OverrideAble;
				case FieldComposition.ComplexNumber:
					DataValue GetValue_Complex (IDataFieldContext context, FieldQuery query)
					{
						DataValue result = default;
						//override
						DataValue override_val = context.GetValue (key_override, query.RefreshValue, query.Parameters);
						if (override_val.HasValue)
						{
							result = override_val;
						}
						else
						{
							//base
							DataValue base_val = context.GetValue (key_base, query.RefreshValue, query.Parameters);
							DataValue base_override = context.GetValue (key_baseOverride, query.RefreshValue, query.Parameters);
							DataValue base_eff = base_override.HasValue ? base_override : base_val;
							if (base_eff.HasValue)
							{
								DataValue base_result = base_eff;
								//additional
								DataValue extra_fixed = context.GetValue (key_extraFixed, query.RefreshValue, query.Parameters);
								DataValue extra_ratio = context.GetValue (key_extraRatio, query.RefreshValue, query.Parameters);
								DataValue baseAndExtra = (extra_ratio + 1) * base_result + extra_fixed;
								//indie multiplier
								DataValue multiplier = context.GetValue (key_multiplier, query.RefreshValue, query.Parameters);
								//result
								result = baseAndExtra * multiplier;
							}
							//indie additional
							DataValue indieExtra = context.GetValue (key_indieExtra, query.RefreshValue, query.Parameters);
							result = result + indieExtra;
						}
						////min max
						//if (result.HasValue)
						//{
						//	DataValue min = context.GetValue (key_min, query.RefreshValue, query.Parameters);
						//	DataValue max = context.GetValue (key_max, query.RefreshValue, query.Parameters);
						//	result.Clamp (min, max);
						//}
						return result;
					}
					return GetValue_Complex;
				default:
					throw new NotSupportedException ($"Not support: {composition}");
			}
		}

		public static IEnumerable<FieldComponents> GetComponents (this FieldComposition composition)
		{
			if (!componentsDict.TryGetValue (composition, out var list))
			{
				list = (from component in (FieldComponents[])Enum.GetValues (typeof (FieldComponents))
						where composition.ContainsComponent (component)
						select component).ToArray ();
				componentsDict[composition] = list;
			}

			return list;
		}
	}
	#endregion
	#region data field
	public class DataFieldSettingObject : SettingObject<DataFieldSetting>
	{
		//unique key
		public string key;
		public string resultKey;
		public FieldComponents componentType = FieldComponents.Result;
		public FieldComposition composition = FieldComposition.None;

		public Type type;

		public int contextFlags;
		public bool editable = true;//can edit by effector
		public bool isContextLocal = false;

		public string showName;
		public string showName_full;

		public DataValue initValue = default;
		public DataValueCombiner combiner;
		public Func<DataValue, DataValue> validator;

		public Func<IDataFieldContext, FieldQuery, DataValue> compositeDataGetter;

		public int debugMode = 0;

		public bool allowCache = true;

		public List<FieldParamSetting> paramSettings;

		#region utility
		public void SetKey (string key, FieldComponents componentType)
		{
			this.key = componentType.GetKey (key);
			this.resultKey = key;
			this.componentType = componentType;
		}

		public void SetKey (string key)
		{
			this.key = key;
			this.resultKey = key;
			this.componentType = FieldComponents.Result;
		}

		public void SetShowName (string showName, FieldComponents component)
		{
			this.showName = showName;
			this.showName_full = showName + component.GetShowNameSuffix ();
		}

		public void SetMinMaxValidator (object min, object max)
		{
			if (min == null && max == null)
			{
				return;
			}

			decimal? _min = min == null ? (decimal?)null : Convert.ToDecimal (min);
			decimal? _max = max == null ? (decimal?)null : Convert.ToDecimal (max);

			DataValue Validator (DataValue dataValue)
			{
				dataValue.Clamp (_min, _max);
				return dataValue;
			}
			validator = Validator;
		}

		public void SetCompositionValueGetter (FieldComposition composition)
		{
			if (composition.AllowsType (type))
			{
				compositeDataGetter = composition.GetValueGetter (key);
			}
			else
			{
				throw new NotSupportedException ($"Type [{type}] cannot use composition [{composition}]");
			}
		}
		#endregion
	}



	public class DataFieldSetting : Setting<DataFieldSettingObject>
	{
		public string Key => SettingObject.key;
		public string ResultKey => SettingObject.resultKey;
		public FieldComponents ComponentType => SettingObject.componentType;
		public FieldComposition Composition => SettingObject.composition;
		public Type Type => SettingObject.type;
		public string ShowName => SettingObject.showName;
		public string ShowName_Full => SettingObject.showName_full;

		public int ContextFlags => SettingObject.contextFlags;
		public bool Editable => SettingObject.editable;
		public bool IsContextLocal => SettingObject.isContextLocal;

		public DataValue InitValue => SettingObject.initValue;

		public int DebugMode => SettingObject.debugMode;

		public bool AllowCache => SettingObject.allowCache;

		public bool IsComposedField => SettingObject.compositeDataGetter != null;
		public bool IsBase => ComponentType == FieldComponents.Base;
		public bool IsResult => ComponentType == FieldComponents.Result;
		public bool IsCustom => IsResult && !IsComposedField;


		public bool IsNumber => SettingObject.type == typeof (decimal);
		public bool IsBool => SettingObject.type == typeof (bool);
		public bool IsEnum => SettingObject.type?.IsEnum ?? false;
		public bool IsString => SettingObject.type == typeof (string);
		public bool IsType (Type type) => SettingObject.type == type;
		public bool IsType<T> () => SettingObject.type == typeof (T);


		public ReadOnlyCollection<FieldParamSetting> ParamSettings { get; private set; }
		public int ParamCount => ParamSettings.Count;

		protected override void Init ()
		{
			base.Init ();
			ParamSettings = SettingObject.paramSettings.ToReadOnly ();
		}

		public DataValue CombineValue (DataValue a, DataValue b)
		{
			return (SettingObject.combiner ?? DataValueCombiners.Override) (a, b);
		}

		public DataValue ValidateValue (DataValue value)
		{
			return SettingObject.validator?.Invoke (value) ?? value;
		}

		public static implicit operator string (DataFieldSetting field)
		{
			return field?.Key ?? "";
		}

		public DataValue GetComposedData (IDataFieldContext context, FieldQuery query)
		{
			if (IsResult)
			{
				return SettingObject.compositeDataGetter.Invoke (context, query);
			}
			else
			{
				throw new Exception ("Only result field have composed data");
			}
		}

		public DataValue QueryValue (IDataFieldContext context, bool refreshValue = false, IList<DataValue> parameters = null)
		{
			var query = new FieldQuery (this, context, parameters);
			query.RefreshValue = refreshValue;
			return context.QueryField (query);
		}
	}
	#endregion
}